using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ExtjsDocsParser;
using HtmlAgilityPack;

static internal class HtmlReader
{

    internal static void ParseDocs(HtmlDocument doc, ExtClassDoc classModel)
    {
        doc.LoadHtml(FixHtmlErrors(classModel.Html));
        var docNode = doc.DocumentNode;
        Debug.Assert(docNode.NodeType == HtmlNodeType.Document);


        var docReader = docNode.Read();
        var rootDiv = docReader.Div;
        docReader.AssertNoMore();

        var rootReader = rootDiv.Read();

        classModel.Hierarchy = ReadHierarchy(rootReader.Pre.Read());
        classModel.Docs = ReadDocs(rootReader);

        var thirdNode = rootReader.Div;
        
    }

    private static string FixHtmlErrors(string html)
    {
        return html.Replace("<h4 class='members-subtitle'>Instance Methods</h3>",
                            "<h4 class='members-subtitle'>Instance Methods</h4>");
    }

    private static Documentation ReadDocs(HtmlNodesReader rootReader)
    {
        var result = new Documentation
        {
            Class = ReadClassDoc(rootReader)
        };
        var membersNode = rootReader.Div;
        Debug.Assert(membersNode.OfClass("members"));

        var membersReader = membersNode.Read();
        result.Configs = ReadMembers(membersReader, "cfg");
        result.Properties = ReadMembers(membersReader, "property");
        result.Methods = ReadMethods(membersReader, "method");
        result.Events = ReadMethods(membersReader, "event");

        //result.Members = members.ToArray();
        return result;
    }

    private static MemberDoc[] ReadMembers(HtmlNodesReader reader, string ext)
    {
        string prefix = ext + "-";
        var members = new List<MemberDoc>();
        var configDiv = reader.Div;
        Debug.Assert(configDiv.OfId("m-" + ext));
        var configReader = configDiv.Read();
        Debug.Assert(configReader.Div.OfClass("definedBy"));
        Debug.Assert(configReader.H3.OfClass("members-title"));
        var subSection = configReader.Div;
        Debug.Assert(subSection.OfClass("subsection"));
        ReadConfigMembers(subSection.Read(), members, prefix);
        return members.ToArray();
    }

    private static MemberDoc[] ReadMethods(HtmlNodesReader reader, string ext)
    {
        string prefix = ext + "-";
        var members = new List<MemberDoc>();
        var configDiv = reader.Div;
        Debug.Assert(configDiv.OfId("m-" + ext));
        var configReader = configDiv.Read();
        Debug.Assert(configReader.H3.OfClass("members-title"));
        var subSection = configReader.Div;
        Debug.Assert(subSection.OfClass("subsection"));
        var subSectionReader = subSection.Read();
        Debug.Assert(subSectionReader.Div.OfClass("definedBy"));
        subSectionReader.H4("Instance Methods");
        ReadMethods(subSectionReader, members, prefix);
        return members.ToArray();
    }

    private static void ReadMethods(HtmlNodesReader reader, List<MemberDoc> members, string prefix)
    {
        while (reader.HasMoreElements)
        {
            var idDiv = reader.Div;
            var doc = new MethodDoc();
            Debug.Assert(idDiv.Id.StartsWith(prefix));
            doc.Name = idDiv.Id.Substring(prefix.Length);
            var reader2 = idDiv.Read();
            Debug.Assert(reader2.HRef != null);
            var titleNode = reader2.Div;
            Debug.Assert(titleNode.OfClass("title"));
            var metaNode = titleNode.Read().Div;
            Debug.Assert(metaNode.OfClass("meta"));
            doc.ClassName = metaNode.Read().HRef.InnerText;
            var descrNode = reader2.Div;
            Debug.Assert(descrNode.OfClass("description"));
            var descrReader = descrNode.Read();
            var shortDescr = descrReader.Div;
            Debug.Assert(shortDescr.OfClass("short"));
            doc.ShortDescription = shortDescr.InnerText;
            var longDescr = descrReader.Div;
            Debug.Assert(longDescr.OfClass("long"));
            var longDescReader = longDescr.Read();
            doc.LongDescription = longDescReader.P.InnerText;

            string extraDesc = "";
            while (longDescReader.HasMore && !longDescReader.AtH3OfClass("pa"))
            {
                extraDesc += longDescReader.Next.OuterHtml;
            }

            doc.LongDescription += extraDesc;

            if (longDescReader.HasMoreElements)
            {
                longDescReader.H3.OfClass("pa");
                var paramList = longDescReader.Ul.Read();
                var paramz = new List<ParamDoc>();
                while (paramList.HasMoreElements)
                    paramz.Add(ReadParameter(paramList, false));
                doc.Params = paramz.ToArray();
            }
            if (longDescReader.HasMoreElements)
            {
                longDescReader.H3.OfClass("pa");
                var returnList = longDescReader.Ul.Read();
                Debug.Assert(returnList.HasMoreElements);
                doc.Returns = ReadParameter(returnList, true);
            }
            members.Add(doc);
        }
    }

    private static ParamDoc ReadParameter(HtmlNodesReader paramList, bool returns)
    {
        var param = new ParamDoc();
        var paramReader = paramList.Li.Read();
        var types = new List<ParamOverload>();
        if (!returns)
        {
            param.Name = paramReader.Span.InnerText;
            if (paramReader.AtText)
            {
                var semicolon = paramReader.Text;
                Debug.Assert(semicolon.Trim() == ":");
                types.Add(new ParamOverload {Type = paramReader.HRef.InnerText});
                while (!paramReader.AtDiv)
                {
                    var slash = paramReader.Text.Trim();
                    if (slash.StartsWith("[]"))
                    {
                        types.Last().Type += "[]";
                        slash = slash.Substring(2);
                    }
                    if (slash.Contains("(optional)"))
                    {
                        types.Last().Optional = true;
                        slash = slash.Replace("(optional)", "");
                    }
                    slash = slash.Replace("/HTMLElement", "");
                    slash = slash.Trim();
                    if (slash == "..." || slash == "")
                        break;
                    Debug.Assert(slash == "/");
                    types.Add(new ParamOverload {Type = paramReader.HRef.InnerText});
                }
            }
        }
        else
        {
            var reader = paramReader.Span.Read();
            types.Add(new ParamOverload { Type = reader.HRef.InnerText });
            while (reader.HasMore)
            {
                var slash = reader.Text;
                if (slash.StartsWith("[]"))
                {
                    types.Last().Type += "[]";
                    slash = slash.Substring(2);
                }
                Debug.Assert(slash == "/");
                types.Add(new ParamOverload { Type = reader.HRef.InnerText });
            }
            
        }
        param.Overloads = types.ToArray();
        var paramDescDiv = paramReader.Div;
        Debug.Assert(paramDescDiv.OfClass("sub-desc"));
        var paramDescReader = paramDescDiv.Read();
        if (!returns)
        {
            param.Doc = paramDescReader.P.InnerText;
            while (paramDescReader.AtText || paramDescReader.AtPre || paramDescReader.AtP)
                param.Doc += paramDescReader.Next.OuterHtml;

            if (paramDescReader.HasMoreElements)
            {
                var optionListReader = paramDescReader.Ul.Read();
                int i = 0;
                while (optionListReader.HasMoreElements)
                {
                    var optionDoc = optionListReader.Li;
                    if (i < param.Overloads.Length-1)
                        param.Overloads[i++].Doc = optionDoc.InnerText;
                    else
                        param.Overloads[i].Doc += optionDoc.InnerText;
                }
            }
        }
        return param;
    }

    private static void ReadConfigMembers(HtmlNodesReader reader, List<MemberDoc> members, string prefix)
    {
        while (reader.HasMoreElements)
        {
            var idDiv = reader.Div;
            var doc = new MemberDoc();
            Debug.Assert(idDiv.Id.StartsWith(prefix));
            doc.Name = idDiv.Id.Substring(prefix.Length);
            var reader2 = idDiv.Read();
            Debug.Assert(reader2.HRef != null);
            var titleNode = reader2.Div;
            Debug.Assert(titleNode.OfClass("title"));
            var metaNode = titleNode.Read().Div;
            Debug.Assert(metaNode.OfClass("meta"));
            doc.ClassName = metaNode.Read().HRef.InnerText;
            var descrNode = reader2.Div;
            Debug.Assert(descrNode.OfClass("description"));
            var descrReader = descrNode.Read();
            var shortDescr = descrReader.Div;
            Debug.Assert(shortDescr.OfClass("short"));
            doc.ShortDescription = shortDescr.InnerText;
            var longDescr = descrReader.Div;
            Debug.Assert(longDescr.OfClass("long"));
            doc.LongDescription = longDescr.InnerHtml;
            
            members.Add(doc);
        }
    }

    private static string ReadClassDoc(HtmlNodesReader rootReader)
    {
        var classDocNode = rootReader.Div;
        Debug.Assert(classDocNode.Attributes["class"].Value == "doc-contents");
        return classDocNode.InnerHtml;
    }

    private static Hierarchy ReadHierarchy(HtmlNodesReader nodes)
    {
        var result = new Hierarchy();

        nodes.H4("Alternate names");
        result.AlternateName = nodes.Div.InnerText;

        nodes.H4("Hierarchy");
        result.Ancestors = ReadAncestors(nodes.Div).ToArray();
        
        nodes.H4("Mixins");
        result.Mixins = ReadDivHrefList(nodes).ToArray();

        nodes.H4("Requires");
        result.Requires = ReadDivHrefList(nodes).ToArray();

        nodes.H4("Files");
        result.Files = ReadDivHrefList(nodes).ToArray();

        nodes.AssertNoMore();
        return result;
    }

    private static List<string> ReadDivHrefList(HtmlNodesReader reader)
    {
        var result = new List<string>();

        while (reader.AtDiv)
        {
            result.Add(new HtmlNodesReader(reader.Div).HRef.InnerText);                
        }
        return result;
    }

    private static List<string> ReadAncestors(HtmlNode node)
    {
        var result = new List<string>();
        AppendAncestor(result, node);
        return result;
    }

    private static void AppendAncestor(List<string> result, HtmlNode node)
    {
        var reader = new HtmlNodesReader(node);
        if (reader.AtHRef)
        {
            var s = reader.HRef.InnerText;
            AppendAncestor(result, reader.Div);
            result.Add(s);
        }
    }
}